# -*- coding: utf-8 -*-
"""
@Time ： 2023/9/26 19:03
@Auth ： weiwenman
@IDE ：PyCharm
"""
import openpyxl
import os
import re
import requests
from bs4 import BeautifulSoup
import logging
import threading
from django.http import JsonResponse
import json

# 在全局创建一个线程锁对象
lock = threading.Lock()
# 创建一个名为__name__的logger对象，用于记录日志信息
logger = logging.getLogger(__name__)


# 用于常规上报校验参数（v，av，c..unv...），比如缺失、冗余
def compare_params(all_params, actual_params, important_params, special_params):
    """
    校验参数
    该函数用于校验上报参数的完整性和正确性。
    :param special_params:
    :param all_params: 全部上报参数，类型为列表
    :param important_params: 重要上报参数，类型为列表
    :param actual_params: 真实上报参数，类型为字典
    :return:
        required_params_missing: 缺失的参数，类型为列表
        extra_params: 多余的参数，类型为字典
        passed_params_sorted: 校验通过的参数（经过排序），类型为字典
        import_passed_params: 通过校验的重要参数，类型为字典
        right_values: 正确取值的参数，类型为字典
        wrong_values: 错误取值的参数，类型为字典
    """

    # 计算缺失的参数
    required_params_missing = list(set(all_params) - set(actual_params.keys()))

    # 计算多余的参数，并转成字典
    extra_params_key = list(set(actual_params.keys()) - set(all_params))
    extra_params = {k: actual_params[k] for k in set(extra_params_key) & set(actual_params.keys())}

    # 计算校验通过的参数字典，并按首字母排序
    passed_params = {k: actual_params[k] for k in set(all_params) & set(actual_params.keys())}
    passed_params_sorted = dict(sorted(passed_params.items(), key=lambda x: x[0]))

    # 解析 viewmonitor 参数的值，并将其转换为字符串，每个参数用竖线分割
    if 'viewmonitor' in passed_params_sorted:
        viewmonitor_parsed = parse_viewmonitor(passed_params_sorted['viewmonitor'])
        if viewmonitor_parsed is not None:
            viewmonitor_str = '|'.join(["{}={}".format(key, value) for key, value in viewmonitor_parsed.items()])
            passed_params_sorted['viewmonitor'] = str(viewmonitor_str)

    # 计算通过校验的重要参数字典
    import_passed_params = {param: actual_params.get(param, "重要参数缺失，无法获取对应值") for param in important_params}
    # import_passed_params = {k: actual_params[k] for k in important_params if k in actual_params}
    # special_passed_params = {k: actual_params[k] for k in special_params if k in actual_params}
    special_passed_params = {param: actual_params.get(param, "特殊参数缺失，无法获取对应值") for param in special_params}
    # import_passed_params = important_params
    # special_passed_params = special_params
    # 检查参数值是否正确，并返回正确取值和错误取值的参数字典
    right_values, wrong_values = check_report_params_value(passed_params_sorted)

    return required_params_missing, extra_params, passed_params_sorted, import_passed_params, special_passed_params, right_values, wrong_values


# 用于处理常规上报校验结果返回内容
def check_report_result_details(required_params_missing, extra_params, passed_params_sorted, import_passed_params,
                                special_passed_params,
                                right_values,
                                wrong_values):
    """
    :param required_params_missing:
    :param extra_params:
    :param passed_params_sorted:
    :param import_passed_params:
    :param special_passed_params:
    :param right_values:
    :param wrong_values:
    :return:
    """
    result = {
        # '---------------------------------------💚💚💚🍀校验结果🍀💚💚💚---------------------------------------': '-',
        '[校验结果]': '【✅成功✅】：参数不多不少且值正确',
    }
    if required_params_missing or extra_params or wrong_values:
        error_messages = []
        result[
            '--------------------------------------- ↓ 【❌失败】检验详情如下 ↓ ---------------------------------------'] = '-'
        if required_params_missing:
            error_messages.append('[1]参数缺失')
            result['缺失参数 →'] = ','.join(required_params_missing)
        if extra_params:  # 直接使用列表作为条件判断，因为一个空列表会被视为 `False`，非空列表会被视为 `True`
            error_messages.append('[2]存在冗余参数')
            result['新增参数 ↓'] = extra_params
        if wrong_values:
            error_messages.append('[3]存在参数取值异常')
            result['【×校验不通过】---参数存在，但值不符合预期，需要检查'] = wrong_values
        if error_messages:
            result['[校验结果]'] = '【❌失败❌】-原因 →' + ''.join(error_messages) + '！'

    if special_passed_params:
        result[
            '---------------------------------------🍉🍉🍉😁[特殊参数]展示😋🍉🍉🍉---------------------------------------'] = '-'
        result['特殊参数 ↓'] = special_passed_params

    if import_passed_params:
        result[
            '---------------------------------------🍉🍉🍉😁[重要参数]展示😋🍉🍉🍉---------------------------------------'] = '-'
        result['重要参数 ↓'] = import_passed_params

    if right_values:
        result[
            '---------------------------------------🍉🍉🍉😁字段值校验结果😋🍉🍉🍉---------------------------------------'] = '-'
        result['【√已校验通过】---参数存在且值正确'] = right_values

    if not wrong_values:
        result['【×校验不通过】---参数存在，但值不符合预期，需要检查'] = '无异常取值'

    return result


# 用于处理常规上报校验通过参数的值
def check_report_params_value(passed_params):
    """
    用于处理常规上报校验通过参数的值
    :param passed_params: 校验通过的参数，继续校验取值
    :return:返回符合预期与不符合预期的参数以及值
    """
    # 定义预期值字典，使用字典的值存储预期值和预期信息
    expected_values = {
        'appchn': ([r'^\d{4}$'], 'appchn-渠道号，取值4位数'),  # 使用列表存储正则表达式和预期信息
        'appid': (['news'], 'appid固定取值news'),  # 使用列表存储字符串和预期信息
        'appv': ([r'\d+\.\d+\.\d+'], 'appv-版本号，取值x.y.z '),  # 使用列表存储正则表达式和预期信息
        'asid': ([r'^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}_\d{13}$'],
                 'asid-启动标识，取值id+时间戳(开发生成)'),
        'abposition': ([r'^\d+$'], 'abposition-广告位置，取值数字'),
        'adstyle': (['1', '2'], 'adstyle-软文应该标识，取值0或1(=1硬广,=2软文)'),
        'apid': ([r'^\d{5}$'], 'apid取值广告位id'),
        'ad_abtest': ([r'^$', r'\S'], 'ad_abtest值无需关注'),
        'appdelaytrack': (['0', '1'], 'appdelaytrack-缓存标识,取值0或1(=0非缓存，=1缓存)'),
        'ac': ([r'^\d+$'], 'ac重复展示标识，=1或者正常累加'),
        'build_version': ([r'(\d+\.\d+\.\d+)_(\d+\.\d+\.\d+\.\d+)', r'^(\d+)\.(\d+)\.(\d+)\.(\d+)\.(\d+)$',
                           r'^(\d+)\.(\d+)\.(\d+)\.(\d+)$'],
                          'build_version-客户端打包版本号(Android：appv_构建年月日打包次数_渠道号,iOS：appv.build号)'),
        'cid': ([r'^\d{19}$'], 'cid-用户id,算法生成19位数字'),
        'cacheNum': ([r'^\d+$'], 'cacheNum-联盟缓可用缓存数'),
        'cacheUT': ([r'\d{13}'], 'cacheUT-上次有效更新缓存时间戳'),
        'clickmonitor': (['c', r'^$'], 'clickmonitor取值c或空'),
        'coldstart': (['0', '1'], 'coldstart-是否冷启,取值0=热启,=1冷启'),
        'deeplink': (['0', '1'], 'deeplink-创意呼起开关,取值0或1(=0打开，=1关闭)'),
        'dytype': (['0', '1', '2', r'^$'], 'dytype-多赢类型，取值=0普通广告,=1时刻阅读版,=2媒体号'),
        'delaytrack': (['0', '1'], 'delaytrack-重试上报标识,取值0或1，=0首次上报，=1重试上报'),
        'errorcode': (['0', '1'], 'errorcode-重试上报标识,取值0或1，=0首次上报，=1重试上报'),
        'event': ([r'^\d+$'], 'event-et上报事件类型'),
        'gbcode': ([r'^\d{10}$', r'^\d{6}$'], 'gbcode-地域编码,取值10位地域编码'),
        'h': ([r'^$', r'\S'], 'h值无需关注'),
        'itemspaceid': ([r'\d{5}'], 'itemspaceid-广告位id)'),
        'impid': ([r'\S'], 'impid-广告请求唯一标识(取值引擎生成，不可为空)'),
        'impressionid': ([r'\S'], 'impressionid-广告请求唯一标识,取值引擎生成，不可为空'),
        'landing': (['0', '1', '2', '3'], 'landing-落地页加载状态，取值=0开始加载,=1完成加载,=2加载失败,=3离开'),
        'lc': ([r'^\d+$'], 'lc-加载次数，取值数字'),
        'local': (['0', '1'], 'landing-落地页加载状态，取值=0开始加载,=1完成加载,=2加载失败,=3离开'),
        'login_pid': ([r'^\d{19}$', r'^$'], 'login_pid-搜狐新闻登录用户的id,取值用户端pid'),
        'launch_id': (
            [r'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'], 'launch_id-冷启动唯一标识'),
        'mkey': ([r'\S'], 'mkey广告请求唯一标识(取值引擎生成，不可为空)'),
        'monitorkey': ([r'\S'], 'mkey广告请求唯一标识(取值引擎生成，不可为空)'),
        'news_num': ([r'^\d+$'], 'news_num-信息流每次加载中内容的条数'),
        'newschn': ([r'^\d+$'], 'newschn-新闻频道，取值频道id'),
        'nwt': (['1000', '1001', '1002', '1003', '1004', '1005'],
                'nwt-网络类型,取值=unknown-1000,wifi-1001,2G-1002,3G-1003,,4G-1004,5G-1005'),
        'os': (['iOS', 'Android'], 'os-操作系统,取值iOS或者Android'),
        'path': (['0', '1'], 'path-push进入标识,取值0或者1'),
        'rc': ([r'^\d+$'], 'rc-加载次数，取值数字'),
        'reposition': ([r'^\d+$'], 'reposition-流内广告相对位置'),
        'rr': ([r'^\d+$'], 'rr-刷新次数，取值数字'),
        'report_scene': ([r'^\d+$'], 'report_scene-上报时机，取值数字'),
        'response_time': ([r'^\d+$'], 'response_time-请求到收到广告返回时间,单位ms'),
        'sdkv': ([r'^(\d+)\.(\d+)\.(\d+)$', r'^(\d+)\.(\d+)\.(\d+)\.(\d+)$'], 'sdkv-广告sdk版本号'),
        'span': ([r'^$', r'\S'], 'span-值由广告协议返回'),
        'sv': ([r'iOS\s*\d+\.\d+\.\d+', r'Android\s*\d+\.\d+\.\d+'], 'sv-操作系统+版本号'),
        'scene_type': (['live', 'ad', 'user', 'news', 'mat'], 'scene_type-上报场景，目前取值有live,user,ad,news,mat'),
        'seq': ([r'^\d+$'], '事件序列'),
        'timetag': ([r'\d{13}'], 'timetag-时间戳'),
        'unapid': ([r'^\d+$'], '联盟代码位id示例6287190'),
        'unid': (['baidu'], '联盟id，目前写死baidu'),
        'uevent': ([r'^\d+$'], 'uevent-uet上报事件类型'),
        'viewmonitor': ([r'^$', r'\S'], 'viewmonitor-值由广告协议返回'),
        'vrpttype': ([r'^$', r'\S'], 'vrpttype-控制第三方上报，值由广告协议返回')
    }
    right_values = {}
    wrong_values = {}

    for key, value in passed_params.items():
        if key in expected_values:
            expected_value, expected_info = expected_values[key]
            # 根据预期值的类型进行校验
            if isinstance(expected_value, list):
                match_found = any(re.match(pattern, value) for pattern in expected_value)
                if match_found:
                    right_values[key] = '{} [校验通过，预期值是：{}]'.format(value, expected_info)
                else:
                    wrong_values[key] = '{} [不符合预期，预期值是：{}]'.format(value, expected_info)
            else:
                if value == expected_value:
                    right_values[key] = '{} [校验通过，预期值是：{}]'.format(value, expected_info)
                else:
                    wrong_values[key] = '{} [不符合预期，预期值是：{}]'.format(value, expected_info)
        else:
            right_values[key] = '{} [校验通过,无预期值]'.format(value)

    return right_values, wrong_values

    # right_values = {}
    # wrong_values = {}
    #
    # for key, value in passed_params.items():
    #     if key in expected_values:
    #         expected_value, expected_info = expected_values[key]
    #         # 根据预期值的类型进行校验
    #         if isinstance(expected_value, list) and any(re.match(pattern, value) for pattern in expected_value):
    #             right_values[key] = '{} [校验通过，预期值是：{}]'.format(value, expected_info)
    #         else:
    #             wrong_values[key] = '{} [不符合预期，预期值是：{}]'.format(value, expected_info)
    #     else:
    #         right_values[key] = '{} [校验通过,无预期值]'.format(value)
    #
    # return right_values, wrong_values


# 通过上报参数获取广告位id，广告模板，广告非空或空广告，广告上报操作系统
class AdReportManager:
    """
    通过上报参数获取广告位id，广告模板，广告非空或空广告，广告上报操作系统
    """

    def __init__(self, report_params):
        self.report_params = report_params

    def get_report_itemspaceid(self):
        """
        获取上报广告位id
        :return:广告位id
        """
        ad_id = self.report_params.get('itemspaceid') or self.report_params.get('apid')
        return ad_id if ad_id else None

    def get_report_template(self):
        """
        获取广告模板
        :return: 返回模板英文名
        """
        template = self.report_params.get('template')
        if template:
            return template
        elif 'viewmonitor' in self.report_params:
            ad_viewmonitor = parse_viewmonitor(self.report_params.get('viewmonitor', {}))
            if ad_viewmonitor is not None:
                return ad_viewmonitor.get('template')
            else:
                return None
        else:
            return None

    def get_report_status(self):
        """
        获取上报内status值
        :return: 返回值获取 None
        """
        ad_status = self.report_params.get('status')
        return ad_status

    def get_report_event_value(self):
        """
        获取上报内event值
        :return: 返回值获取 None
        """
        # get方法获取event值，如果不存在键或者为空，则返回默认值None
        event_value = self.report_params.get('event')
        if event_value:
            report_event = 'event=' + str(event_value)
            return report_event
        else:
            return None

    def get_report_ad_status(self):
        """
        获取广告状态：非空或者空广告
        :return:非空或者空广告
        """
        if 'status' in self.report_params and self.report_params['status'] == '0':
            return '空广告'
        else:
            return '非空广告'

    def get_report_ad_os(self):
        """
        获取广告操作系统
        :return:Android或iOS
        """
        if 'os' in self.report_params:
            if 'iOS' in self.report_params['os']:
                return 'iOS'
            elif 'Android' in self.report_params['os']:
                return 'Android'
        elif 'sv' in self.report_params:
            if 'iOS' in self.report_params['sv']:
                return 'iOS'
            elif 'Android' in self.report_params['sv']:
                return 'Android'

        return None


# 获取上报类型
def get_report_type(report_url):
    """
    获取上报类型：v，av，c...
    :param report_url:上报串
    :return:返回上报类型
    """
    report_path = report_url.split('count/')[1]
    real_report_type = report_path.split('?')[0]

    if len(real_report_type) > 0:
        return real_report_type
    else:
        return None


# 获取每条上报对应的系统、广告位、模板、拼接成上报title用于提示
def get_report_title(report_params, report_url):
    """
    获取每条上报对应的系统、广告位、模板、拼接成上报title用于提示
    :param report_url:
    :param report_params: 真实上报参数
    :return:ReportTitle 本次校验广告位为：iOS--12224--开屏广告--多卡切换模板--上报类型为":
    """
    # print(report_url)
    adReportManager = AdReportManager(report_params)
    # 从实际上报字典内 获取操作系统
    ad_os = adReportManager.get_report_ad_os()
    # 从实际上报字典内，获取广告位id，取值itemspaceid或者apid，如果都没有返回none
    ad_itemspace_id = '' if adReportManager.get_report_itemspaceid() is None else adReportManager.get_report_itemspaceid()

    # 获取广告模版template
    ad_template = adReportManager.get_report_template()
    # 获取event
    ad_event_value = adReportManager.get_report_event_value()
    # 判断是非空广告还是空广告上报
    ad_type = adReportManager.get_report_ad_status()
    ad_report_type = get_report_type(report_url)

    script_dir = os.path.dirname(os.path.abspath(__file__))
    ReportTitle = '本次校验上报类型为'
    ad_report_check_case = {}
    if ad_report_type == 'et':
        # 读取文件获取event上报事件映射描述
        event_config_file_path = os.path.join(script_dir, '..', 'CheckParams', 'ad_config_file', 'ad_event.txt')
        with open(event_config_file_path, 'r', encoding='utf-8') as file:
            event_content = file.read()
        # json转成字典格式
        event_config = json.loads(event_content)
        # 获取et全部事件说明
        adv_events = event_config['et']
        # event_text = adv_events[ad_event_value] if ad_event_value in adv_events else ''
        event_text = adv_events.get(ad_event_value, '')
        ReportTitle = '本次校验et上报事件为：{}--{}--{}--{}'.format(ad_os, ad_itemspace_id, ad_event_value, event_text)

    else:
        ad_config_file_path = os.path.join(script_dir, '..', 'CheckParams', 'ad_config_file', 'ad_templates.txt')
        try:
            with open(ad_config_file_path, 'r', encoding='utf-8') as file:
                ad_template_content = file.read()
                templates = json.loads(ad_template_content)
                templates['13016'] = templates['12451']
                templates['13015'] = templates['12452']
                templates['13370'] = templates['12715']
                templates['13371'] = templates['12716']
                adv_templates = templates.get(ad_itemspace_id, '')
                ad_description = adv_templates.get('description', '') if adv_templates else ''
                ad_status = ad_type if ad_type == '空广告' else ''
                ad_template_chinese = adv_templates.get(ad_template, ad_template) if ad_template is not None else ''
                ReportTitle = '本次校验上报内容：{}--{}--{}--{}--{}-上报类型为'.format(ad_os, ad_itemspace_id, ad_description,
                                                                         ad_status,
                                                                         ad_template_chinese)
        except FileNotFoundError:
            # 处理文件不存在的情况
            print("ad_config_file_path not found. Please check the file path or ensure the file exists.")
            templates = {}
            ReportTitle = '本次校验上报内容：{}--{}--{}上报类型为'.format(ad_os, ad_itemspace_id, ad_status)
        except json.JSONDecodeError:
            # 处理JSON解析错误的情况
            print("Failed to parse ad_template_content as JSON.")
            templates = {}
        # adv_templates = templates[ad_itemspace_id] if ad_itemspace_id in templates else ''
        # ad_description = adv_templates['description'] if ad_itemspace_id in templates else ''
        # ad_status = ad_type if ad_type == '空广告' else ''

        # ad_template_chinese = adv_templates[ad_template] if ad_template in adv_templates else ad_template
        # 获取模板描述
        # ad_template_chinese = adv_templates[
        #     ad_template] if ad_template in adv_templates else ad_template if ad_template is not None else ''
        # `adv_templates.get(ad_template, ad_template)` 部分将尝试从 `adv_templates` 中获取 `ad_template` 的值，如果找不到则返回 `ad_template` 的默认值。然后，在添加一个条件，如果 `ad_template` 为 `None`，则将 `ad_template_chinese` 设置为空字符串。

    ad_report_check_case[ReportTitle] = ad_report_type

    return ad_report_check_case


# 获取uet每个上报事件对应的事件名以及事件触发时机
def get_report_event_name(uevent_type):
    """
    获取uet每个上报事件对应的事件名以及事件触发时机
    :param uevent_type: 事件类型，比如 uevent=7
    :return: 返回uevent事件名字和触发时机
    """
    script_dir = os.path.dirname(os.path.abspath(__file__))
    uet_config_file_path = os.path.join(script_dir, '..', 'CheckParams', 'ad_config_file', 'ad_uet.txt')
    with open(uet_config_file_path, 'r', encoding='utf-8') as file:
        uet_content = file.read()
    uet_config = json.loads(uet_content)
    adv_uets = uet_config['uet']
    if uevent_type in adv_uets:
        uet_name = '{}'.format(adv_uets[uevent_type]['name'])
        uet_moment = '{}'.format(adv_uets[uevent_type]['moment'])
    else:
        uet_name = '未知uet上报事件'
        uet_moment = '未知uet上报时机'
    return uet_name, uet_moment


# 根据操作系统读取本地储存的参数表
def get_report_excel(report_os=None, report_type=None):
    """
    根据操作系统读取本地储存的参数表
    :param report_type:
    :param report_os:上报操作系统
    :return:all_params_filepath
    """
    all_params_filepath = ''
    current_dir = os.path.dirname(os.path.abspath(__file__))
    # 判断系统是iOS，读取iOS_Report__Params.xlsx表
    if report_type == 'et':
        all_params_filepath = os.path.join(current_dir, 'ReportDataExcel', 'Event_Report_Params.xlsx')
    elif report_type == 'uet':
        all_params_filepath = os.path.join(current_dir, 'ReportDataExcel', 'Uevent_Report_Params.xlsx')
    else:
        if report_os == 'iOS':
            all_params_filepath = os.path.join(current_dir, 'ReportDataExcel', 'iOS_Report__Params.xlsx')
        # 判断系统是Android，读取Android_Report_Params.xlsx表
        elif report_os == 'Android':
            all_params_filepath = os.path.join(current_dir, 'ReportDataExcel', 'Android_Report_Params.xlsx')

    return all_params_filepath


# 读取本地表格内获取不同上报类型对应的全部参数、重要参数、特殊参数
def get_params_config_from_excel(filepath, worksheet_name, report_event):
    """
    读取表格内获取全部参数、重要参数、特殊参数
    :param filepath: 表格存储路径
    :param worksheet_name: sheet的名字
    :param report_event: 上报类型，也是合并单元格的值，例如v，av，uevent=7，比如是3行合并的单元号，否则报错
    :return: all_parameters, important_parameters, special_parameters
    """
    # 检查文件路径是否存在
    if not os.path.exists(filepath):
        return "文件路径不存在: {}".format(filepath)
    # 加载工作簿和工作表
    workbook = openpyxl.load_workbook(filepath)
    if worksheet_name in workbook.sheetnames:
        sheet = workbook[worksheet_name]
        # 获取表头
        headers = [cell.value for cell in sheet[1]]
        # 检查上报类型是否存在于表头中
        if report_event in headers:
            # 创建表头索引字典 表头值与索引存在一个字典内 例如{'v': 1, None: 9, 'av': 4, 'c': 7}
            header_index = {header: index + 1 for index, header in enumerate(headers)}
            # 获取上报类型的列索引，比如v 列索引是1即，第1列，all是1，important是2，special是3
            report_event_index = header_index[report_event]
            # 确定数据行起始位置，从第3行开始读，第二行 all，import,special顺序不可变
            row_start = 3 if sheet.max_row > 1 else 1
            # 初始化各个列表
            all_parameters = []
            important_parameters = []
            special_parameters = []

            # 遍历每一行，提取对应列的数值,遍历范围从第3行开始，表格最大行结束
            for row in range(row_start, sheet.max_row + 1):
                # 如果v上报列索引是1，则全部参数为第1列，从第3行开始全部列的值
                # sheet.cell(row=row, column=report_event_index).value，指定行row和列column返回单元格的值
                all_params_value = sheet.cell(row=row, column=report_event_index).value
                if all_params_value:
                    all_parameters.append(all_params_value)
                # 如果v上报列索引是1，重要参数为第2列，从第3行开始全部列的值
                important_params_value = sheet.cell(row=row, column=report_event_index + 1).value
                if important_params_value:
                    important_parameters.append(important_params_value)
                # 如果v上报列索引是1，特殊参数为第3列，从第3行开始全部列的值
                special_params_value = sheet.cell(row=row, column=report_event_index + 2).value
                if special_params_value:
                    special_parameters.append(special_params_value)
            # 将3个列表返回
            return all_parameters, important_parameters, special_parameters
        else:
            return "未找到{}--{}上报类型的参数配置，请配置！".format(worksheet_name, report_event)
    else:
        # 工作表不存在，给出错误提示或执行其他容错处理
        return "未找到{}广告位的参数配置，请配置！".format(worksheet_name)
        # 其他容错处理


# 解析viewmonitor并存在将key和value存在字典内
def parse_viewmonitor(viewmonitor_value, url='http://10.18.70.119:4321/'):
    """
    解析viewmonitor并将key和value存在字典内
    :param viewmonitor_value:
    :param url: 该url是引擎组提供的，目前固定是http://10.18.70.119:4321/，如遇到服务不可用请联系引擎的开发
    :return: 返回解析成功的viewmonitor字典，如果url访问失败则返回None
    """
    if not viewmonitor_value:
        return {}  # 如果 viewmonitor_value 为空，则直接返回空字典
    try:
        # 构造POST请求的参数
        data = {
            'viewmonitor': viewmonitor_value,
        }

        # 发送POST请求
        response = requests.post(url, data=data)
        response.raise_for_status()

        # 解析返回的HTML页面
        soup = BeautifulSoup(response.text, 'html.parser')

        # 获取解析结果
        result = soup.find('p').text

        viewmonitor_parsed = {}

        pairs = result.split('\t')  # 使用制表符分割解析结果中的参数和值
        for pair in pairs:
            split_result = pair.split('=')
            # 添加容错逻辑，在拆分之前检查拆分结果的数量。如果拆分结果大于等于2，则将第一个值作为键，将剩余的部分合并并作为值。这样可以确保即使拆分结果不符合预期，也能保证键和值的正确赋值。
            if len(split_result) >= 2:
                key = split_result[0]
                value = '='.join(split_result[1:])
                viewmonitor_parsed[key] = value
            else:
                # 处理无法拆分出键值对的情况
                viewmonitor_parsed[pair] = ''

        # 按key首字母排序
        # viewmonitor_parsed = dict(sorted(viewmonitor_parsed.items(), key=lambda x: x[0]))
        return viewmonitor_parsed

    except requests.RequestException as e:
        # 处理请求异常（例如URL访问失败）
        # 处理请求异常（例如URL访问失败）
        logger.error(f"Error occurred during the request: {str(e)}")
        return None
    except Exception as e:
        # 处理其他异常
        logger.error(f"Error occurred: {str(e)}")
        return None


# 常规上报最终校验结果拼接
def normal_reporting_result(real_report_params, report_url, real_report_type):
    """
    常规上报最终校验结果拼接
    :param report_url:真实上报串
    :param real_report_type: 真实上报类型，v，av，c等
    :param real_report_params:真实上报参数
    :return: 最终校验结果
    """
    adReportManager = AdReportManager(real_report_params)

    # 从实际上报字典内，获取广告位id，取值itemspaceid或者apid，如果都没有返回none
    ad_itemspace_id = adReportManager.get_report_itemspaceid()

    # 从实际上报字典内，获取实际操作系统，取值os或者sv，如果都没有返回none
    ad_os = adReportManager.get_report_ad_os()

    ad_event_value = adReportManager.get_report_event_value()
    if ad_os is None:
        check_result = {'[校验结果]': '【❌失败】：os或者sv参数缺失或者值异常,无法确认上报操作系统！'}
        log_check_result(check_result, report_url)
        check_result = JsonResponse(check_result, json_dumps_params={'ensure_ascii': False})
        return check_result
    else:
        all_params_filepath = get_report_excel(ad_os, real_report_type)
        if real_report_type == 'et':
            if ad_event_value is None:
                check_result = {'[校验结果]': '【❌失败】：当前上报类型为et,但[event参数缺失或者值为空]-无法校验'}
                log_check_result(check_result, report_url)
                check_result = JsonResponse(check_result, json_dumps_params={'ensure_ascii': False})
                return check_result
            else:
                get_params_config = get_params_config_from_excel(all_params_filepath, ad_os, ad_event_value)
        else:
            if ad_itemspace_id is None:
                check_result = {'[校验结果]': '【❌失败】：无法获取当前上报类型广告位id-[缺失广告位id或者值为空]-无法校验'}
                log_check_result(check_result, report_url)
                check_result = JsonResponse(check_result, json_dumps_params={'ensure_ascii': False})
                return check_result
            else:
                get_params_config = get_params_config_from_excel(all_params_filepath, ad_itemspace_id,

                                                                 real_report_type)

        if isinstance(get_params_config, str):
            check_result = {
                '[校验结果]': '【❌失败】：{}'.format(get_params_config)}
            log_check_result(check_result, report_url)
            check_result = JsonResponse(check_result, json_dumps_params={'ensure_ascii': False})
            return check_result
        else:
            all_report_params, important_params, special_params = get_params_config
            if len(all_report_params) == 0:
                check_result = {
                    '[校验结果]': '【❌失败】：请在公共参数配置表内配置{}-{}上报需要校验参数'.format(ad_itemspace_id, real_report_type)}
                log_check_result(check_result, report_url)
                check_result = JsonResponse(check_result, json_dumps_params={'ensure_ascii': False})
                return check_result
            else:
                ad_report_tips = get_report_title(real_report_params, report_url)
                # 根据获取到的全部参数，重要参数，以及真实上报参数，调用校验参数函数进行对比，获取到缺失参数、多余参数、已经通过的参数、重要参数、符合预期的值、不符合预期的值
                required_params_missing, extra_params, passed_params_sorted, import_passed_params, special_passed_params, right_values, wrong_values = compare_params(
                    all_report_params, real_report_params, important_params, special_params)
                #
                #  传入上述获取到的参数，获取当前校验返回结果
                parameter_check_result_details = check_report_result_details(required_params_missing, extra_params,
                                                                             passed_params_sorted,
                                                                             import_passed_params,
                                                                             special_passed_params, right_values,
                                                                             wrong_values)
        #  将广告id和上报类型与校验返回结果组合成最终校验结果
        check_result = {**ad_report_tips, **parameter_check_result_details}  # 上报类型这里可以删
        log_check_result(check_result, report_url)
        check_result = JsonResponse(check_result, json_dumps_params={'ensure_ascii': False})

    return check_result


# uet参数校验，目前只校验了每个uevent事件参数的缺失、新增，值暂未校验
def compare_uet_params(config_all_uet_params, real_report_uet_params, uevent_type):
    """
    uet参数校验，检验uevent事件参数缺失情况
    :param config_all_uet_params: 本地表内uevent上报事件参数，数组类型
    :param real_report_uet_params: 真实上报uevent参数归类详情，原始数据加工处理成 每个uevent数据被存储在{'details'}:[...]}数组内，意图是方便校验该事件的每一条上报数据
    {'[事件名称]': 'APP启动-冷启动', '[上报时机]': '[1]-app进入-冷启动;[2]-Push进入（已杀进程）;[3]-端外呼起（已杀进程）', '[uevent=7事件-上报总数]': 0, 'details': [{'scene_type': 'user', 'asid': '7366686F-4648-4486-97CF
-2D9076DB61A7_1698285268565', 'uevent': '7', 'timetag': '1698285268632', 'nwt': '1001', 'build_version': '7.0.60.224.1', 'launch_id': '483A6475-14CA-4BDE-A086-9AE0CD6C8B1F', 'appv': '7.0.6', 'seq': '1'}]}
uet_event_value:{'[事件名称]': 'APP启动-后台进入前台', '[上报时机]': '[1]-app进入后台再返回;[2]-Push进入（未杀进程）;[3]-端外呼起（未杀进程）;[4]-app进入-热启动（仅安卓）;[5]-锁屏再打开;[6]-下拉通知栏返回,会同时上报三个事件，uet=8,9
,8（仅iOS）', '[uevent=8事件-上报总数]': 0, 'details': [{'scene_type': 'user', 'asid': '7366686F-4648-4486-97CF-2D9076DB61A7_1698285268565', 'uevent': '8', 'timetag': '1698285268706', 'nwt': '1001', 'build_version': '7.0.60.224.1',
'launch_id': '483A6475-14CA-4BDE-A086-9AE0CD6C8B1F', 'appv': '7.0.6', 'seq': '2'}]}
    :param uevent_type: 事件类型，例如uevent=x
    :return: uet_compare_result校验结果，成功或者失败的字典
    """
    # 定义预期值字典，使用字典的值存储预期值和预期信息
    # 初始化uet_compare_result为uet最终校验结果
    uet_compare_result = ''
    expected_keys = set(config_all_uet_params)
    fail_result = {
        f"[{uevent_type}事件-校验结果]-->失败": {
            "失败原因": "存在冗余参数",
            "失败详情": []
        }
    }
    success_result = {
        f"[{uevent_type}事件-校验结果]-->成功": "参数不多不少且值正确"
    }
    # print(real_report_uet_params["details"])
    # 使用 enumerate() 函数遍历 real_report_uet_params["details"] 列表，并同时获取索引值和元素值
    for index, uevent_data in enumerate(real_report_uet_params["details"]):
        # 定义一个集合变量 actual_keys 用于存储实际的键
        right_values, wrong_values = check_report_params_value(uevent_data)
        actual_keys = set()
        # 遍历 uevent_data 字典的键值对
        for key, value in uevent_data.items():
            # 将当前键添加到 actual_keys 集合中
            actual_keys.add(key)
            # 检查当前值是否为字典类型
            if isinstance(value, dict):
                # 将当前值的键集合添加到 actual_keys 中
                actual_keys.update(value.keys())

        missing_params = expected_keys - actual_keys
        redundant_params = actual_keys - expected_keys
        # expected = actual_keys.intersection(expected_keys)
        # 取不到值时，这几个字段就不会上报，移除缺失参数
        if uevent_type in ['uevent=7', 'uevent=8', 'uevent=9', 'uevent=10', 'uevent=11', 'uevent=12']:
            ignore_params = {'page_event_id', 'refer', 'page_info'}
            missing_params.difference_update(ignore_params)
        #
        # ignore_params_redundant = {'delayreport', 'asid'}
        # redundant_params.difference_update(ignore_params_redundant)
        comparison_result = {
            "缺失参数": list(missing_params),
            "冗余参数": list(redundant_params),
        }

        if missing_params or redundant_params or wrong_values:
            if missing_params and redundant_params and wrong_values:
                comparison_result["取值异常"] = wrong_values
                fail_reason = "存在缺失和冗余参数且值异常，请查看详情"
            elif missing_params and wrong_values:
                comparison_result.pop("冗余参数", None)
                comparison_result["取值异常"] = wrong_values
                fail_reason = "存在缺失参数且值异常，请查看详情"
            elif redundant_params and wrong_values:
                comparison_result.pop("缺失参数", None)
                comparison_result["取值异常"] = wrong_values
                fail_reason = "存在冗余参数且值异常，请查看详情"
            elif missing_params:
                comparison_result.pop("冗余参数", None)
                fail_reason = "存在缺失参数，请查看详情"
            elif redundant_params:
                comparison_result.pop("缺失参数", None)
                fail_reason = "存在冗余参数，请查看详情"
            elif wrong_values:
                comparison_result.pop("缺失参数")
                comparison_result.pop("冗余参数")
                comparison_result["取值异常"] = wrong_values
                fail_reason = "取值异常，请查看详情"
            fail_result[f"[{uevent_type}事件-校验结果]-->失败"]["失败原因"] = fail_reason
            fail_result[f"[{uevent_type}事件-校验结果]-->失败"]["失败详情"].append(
                {f"[{uevent_type}[{index}]事件-校验详情]--->": comparison_result})
            uet_compare_result = fail_result
        else:
            # success_result[f"[{uevent_type}参数-预期取值]"] = right_values
            uet_compare_result = success_result

    return uet_compare_result


def uet_reporting_result(uet_real_report_data):
    """
    获取最终uet事件校验结果
    :param uet_real_report_data: 真是uet上报数据字典类型
    :return: uet_check_result 校验结果
    """
    # uet全事件校验结果字典
    uet_verification_conclusion = {
        '本次uet上报校验结果如下↓': {'[校验结果]': '', '[上报总数]': 0, '[校验通过]': 0, '[校验失败]': 0, '[校验详情]': []},
        '-': '------------------------------------具体每个uevent事件校验详情如下↓-------------------------------------'
    }
    # 上报失败条数，初始0
    fail_report_counts_totals = 0
    # 上报成功条数，初始0
    success_report_counts_totals = 0
    # 初始化空字典，用于存放每个uevent事件详细校验结论
    uet_real_report_summary = {}
    # uet本地存储参数文件路径
    uet_params_filepath = get_report_excel(0, 'uet')
    # 遍历uet_real_report_data中的"data_list"列表，获取单个uevent上报事件赋值给给uet_event_dict变量，生成每个uevent事件校验详情存在uet_real_report_summary内
    for uet_event_dict in uet_real_report_data["data_list"]:
        # 提取uevent值，拼接成uevent=x字符
        uevent_value = uet_event_dict["uevent"]
        uet_event_v = "uevent=" + uevent_value
        # 根据uevent=x 获取到每个uevent事件的事件名和上报时机
        uet_name, uet_moment = get_report_event_name(uet_event_v)
        # 如果当前事件的字符串表示`uet_event_v`不在`uet_real_report_summary`中
        if uet_event_v not in uet_real_report_summary:
            # 会创建一个新的键值对，并初始化一些默认值，包括事件名称、上报时机、上报总数为0，以及一个空的详细信息列表
            uet_real_report_summary[uet_event_v] = {
                "[事件名称]": uet_name,
                "[上报时机]": uet_moment,
                f"[{uet_event_v}事件-上报总数]": 0,
                "details": []
            }

        # 将当前事件每条上报数据添加到的详细信息列表中
        uet_real_report_summary[uet_event_v]["details"].append(uet_event_dict)
    # 遍历`uet_real_report_summary`中的每一项，key和value，分别使用`uet_event_key`和`uet_event_value`表示
    for uet_event_key, uet_event_value in uet_real_report_summary.items():
        # 获取每个事件总的上报数量，并赋值
        uevent_counts = len(uet_event_value["details"])
        uet_event_value[f"[{uet_event_key}事件-上报总数]"] = uevent_counts
        # 从Excel文件中读取`uet_params_filepath`路径下的配置信息，获取所有上报参数、重要参数和特殊参数，分别存储在`all_report_params`、`important_params`和`special_params`变量中
        all_report_params, important_params, special_params = get_params_config_from_excel(uet_params_filepath, 'uet',
                                                                                           uet_event_key)

        # 使用`compare_uet_params`函数比较上报参数和`uet_event_value`字典中对应事件的值，并将比较结果存储在`uet_compare_result`字典中
        uet_compare_result = compare_uet_params(all_report_params, uet_event_value, uet_event_key)
        # 设置失败的键名为`fail_key`，格式为`[{uet_event_key}事件-校验结果]-->失败`。
        fail_key = f"[{uet_event_key}事件-校验结果]-->失败"
        # 初始化`结论校验详情列表verification_details_list`、单条校验详情字典`verification_details_dict`和`uet_conclusion`变量。
        verification_details_list = []
        verification_details_dict = {}
        uet_conclusion = uet_verification_conclusion['本次uet上报校验结果如下↓']
        # 初始化失败次数`fail_counts`为0，原因`reason`为'success'
        fail_counts = 0
        reason = 'success'
        # 如果`fail_key`存在于`uet_compare_result`字典中，则获取失败原因和失败详情。将失败原因和失败详情拼接为字符串，赋值给`reason`变量。同时计算失败的条数，更新失败总数。
        if fail_key in uet_compare_result:
            fail_reason = uet_compare_result[fail_key]['失败原因']
            fail_detail = ''.join(str(x) for x in list(uet_compare_result[fail_key]['失败详情'][0].values()))
            reason = f'{fail_reason}-{fail_detail}'
            fail_counts = len(uet_compare_result[fail_key]['失败详情'])
            fail_report_counts_totals += fail_counts
        # 获取成功条数，并累加上报成功条数
        success_counts = uevent_counts - fail_counts
        success_report_counts_totals += success_counts
        # 每条uevent事件共计上报条数，校验通过条数、失败条数
        verification_details = f"校验通过[{success_counts}]条，失败[{fail_counts}]条--------[{reason}]"
        verification_counts = f"[{uet_event_key}]事件共上报[{uevent_counts}]条"
        verification_details_dict[verification_counts] = verification_details
        # 将每条数据更新到校验详情列表内
        verification_details_list.append(verification_details_dict)
        uet_conclusion['[校验详情]'] += verification_details_list
        # 将每条的校验结果都添加到每个uevent=x对应的value内
        uet_event_value.update(uet_compare_result)
        # 删除details键，避免输出结果时重复打印
        details_value = uet_event_value.pop("details", None)
        uet_event_value["details"] = details_value
    # 这段代码可以校验详情内容，按照uevent事件排序
    # aa=uet_conclusion['[校验详情]']
    # sorted_data = sorted(aa, key=lambda x: int(re.search(r'\d+', list(x.keys())[0]).group()))
    # uet_verification_conclusion['本次uet上报校验结果如下↓']['[校验详情]']=sorted_data
    # 更新校验结果总结
    if fail_report_counts_totals > 0:
        uet_conclusion['[校验结果]'] = '[校验不通过]'
    else:
        uet_conclusion['[校验结果]'] = '[校验通过]'
    # 更新校验结论
    uet_conclusion['[校验通过]'] = success_report_counts_totals
    uet_conclusion['[校验失败]'] = fail_report_counts_totals
    uet_conclusion['[上报总数]'] = success_report_counts_totals + fail_report_counts_totals
    # 对校验详情按照uevent的值排序
    sorted_result = dict(sorted(uet_real_report_summary.items(), key=lambda x: int(x[0].split("=")[1])))
    # 将结论和详情拼接在一起
    uet_check_result = {**uet_verification_conclusion, **uet_real_report_summary}

    return uet_check_result


# count可以改，对应url文件路由views.count也需要改，count逻辑是常规上报类型的校验，比如et的可以单独写一个函数，区分于常规的参数校验
def count(request):
    """
    校验核心处理逻辑：获取真实上报串，根据上报串获取真实上报类型，判断真实上报串内是否可以获取广告位id和操作系统，再去读取对应全部预期上报参数，调用函数对比，返回校验结果

    :param request:上报请求
    :return:校验结果
    """
    # 获取真实上报串
    report_url = request.build_absolute_uri()
    # 获取真实上报类型
    real_report_type = get_report_type(report_url)

    if request.method == 'POST':
        uet_data = request.body.decode('utf-8')
        # 将原始数据为json字符串，转换成字典格式
        uet_real_report_data = json.loads(uet_data)
        # 传入真实uet上报数据，获取uet校验结果 dict格式
        uet_check_result_dict = uet_reporting_result(uet_real_report_data)
        # uet_reporting_json_data = json.loads(uet_reporting)
        originally_return = {
            'message': 'success',
            'status': 0,
            'data': True
        }

        check_result = {**uet_check_result_dict, **originally_return}
        check_result = JsonResponse(check_result, json_dumps_params={'ensure_ascii': False})
    else:
        # get请求的话，将获取到的上报串参数key与value分割储存到字典内，获取实际上报的全部参数与值
        real_report_params = {k: v for k, v in request.GET.items()}
        check_result = normal_reporting_result(real_report_params, report_url, real_report_type)

    return check_result


def log_check_result(check_result, original_report_url=None):
    """
    记录校验结果到日志文件
    :param check_result: 校验结果字典
    :param original_report_url: 请求对象
    """
    try:
        with lock:  # 使用with语句获取线程锁
            if any('失败' in value for value in check_result.values()):
                if any('缺失' in value for value in check_result.values()):
                    fail_reason = "{} {}".format(check_result['[校验结果]'], check_result['缺失参数 →'])
                elif any('冗余' in value for value in check_result.values()):
                    fail_reason = "{} {}".format(check_result['[校验结果]'], check_result['新增参数 ↓'])
                else:
                    fail_reason = str(check_result['[校验结果]'])

                log_message = "校验失败 - {} 上报串为-->{}".format(fail_reason, original_report_url)
                logger.error(log_message)

    except Exception as e:
        logger.error('日志记录出错: ' + str(e))
